Bank.java

  1: // Bank.java - A demo of (un)synchronized threads.
  2: // This program starts two threads, one that transfer $100 from
  3: // checking to savings and back again.  The other transfers $10
  4: // back and forth.  If the "synchronized" keyword is commented
  5: // out from the transfer() method, the balances will eventually
  6: // be wrong and the demo will stop.
  7: //
  8: // Written 2000 by Wayne Pollock, Tampa Florida USA.
  9: // Updated 5/2021: Converted from Applet, updated style.
 10: 
 11: import java.awt.*;
 12: import java.awt.event.*;
 13: 
 14: public class Bank extends Frame implements Runnable, ActionListener {
 15:     private Account checking, savings;
 16:     private volatile boolean demoRunning;
 17:     private static final int totalBal = 1000;
 18:     private volatile Thread t1, t2;
 19:     private static final int t1TransferAmount = 100;
 20:     private static final int t2TransferAmount = 10;
 21: 
 22:     private Button btn;
 23:     private TextField checkingTF, savingsTF;
 24:     private Panel errorP;
 25: 
 26:     public static void main ( String[] args ) {
 27:         Frame f = new Bank();
 28:         f.setTitle( "Banking Transfer Demo" );
 29:         f.setSize(430, 260);
 30:         f.setLocationRelativeTo( null );
 31:         f.addWindowListener( new WindowAdapter() {
 32:             @Override public void windowClosing( WindowEvent we ) {
 33:                 System.exit(0);
 34:             }
 35:         });
 36:         f.setVisible( true );
 37:     }
 38: 
 39:     public Bank () {
 40:         checking = new Account( totalBal / 2 );
 41:         savings = new Account( totalBal - checking.bal );
 42:         demoRunning = false;
 43: 
 44:         checkingTF = new TextField( 7 );
 45:           checkingTF.setEditable( false );
 46:           checkingTF.setText( " $" + checking.bal );
 47:           checkingTF.setFont( new Font("Monospaced", Font.PLAIN, 14) );
 48: 
 49:         savingsTF = new TextField( 7 );
 50:           savingsTF.setEditable( false );
 51:           savingsTF.setText( " $" + savings.bal );
 52:           savingsTF.setFont( new Font("Monospaced", Font.PLAIN, 14) );
 53: 
 54:         btn = new Button( "Start" );  // This is also the stop button.
 55:           btn.setBackground( Color.green.brighter() );
 56: 
 57:         Label warning = new Label( "Balance Error!", Label.CENTER );
 58:           warning.setFont( new Font( "SansSerif", Font.BOLD, 24 ) );
 59:           warning.setForeground( Color.red );
 60: 
 61:         Label title = new Label( "(Un)Synchronized Thread Demo",
 62:                                       Label.CENTER );
 63:           title.setFont( new Font( "SansSerif", Font.BOLD, 24 ) );
 64: 
 65:         Label footer = new Label( "\u00A9 2021 by Wayne Pollock, "
 66:              + "Tampa FL USA.  All Rights Reserved.", Label.CENTER );
 67:           footer.setFont( new Font( "SansSeriff", Font.PLAIN, 10 ) );
 68: 
 69:         // Layout the components:
 70:         setLayout( new BorderLayout() );
 71:         setBackground( Color.lightGray );
 72:         Panel wrapper;  // Used so buttons and things won't stretch.
 73:         add( title, "North" );
 74:         Panel checkingP = new Panel();
 75:           checkingP.setLayout( new BorderLayout() );
 76:           checkingP.add( new Label( "Checking Bal", Label.CENTER ),
 77:                 "North" );
 78:           checkingP.add( checkingTF, "South" );
 79:           wrapper = new Panel();
 80:             wrapper.add( checkingP );
 81:         add( wrapper, "West" );
 82: 
 83:         Panel savingsP = new Panel();
 84:           savingsP.setLayout( new BorderLayout() );
 85:           savingsP.add( new Label( "Savings Bal", Label.CENTER ),
 86:                                 "North" );
 87:           savingsP.add( savingsTF, "South" );
 88:           wrapper = new Panel();
 89:             wrapper.add( savingsP );
 90:         add( wrapper, "East" );
 91: 
 92:         errorP = new Panel();
 93:           errorP.setLayout( new BorderLayout() );
 94:           errorP.setVisible( false );
 95:           errorP.add( new Label( " " ), "North" );  // A Spacer.
 96:           errorP.add( warning, "Center" );
 97:           Button resetBtn = new Button( " Reset " );
 98:             resetBtn.setBackground( Color.orange );
 99:           wrapper = new Panel();
100:             wrapper.add( resetBtn );
101:           errorP.add( wrapper, "South" );
102:         add( errorP, "Center" );
103: 
104:         Panel bot = new Panel();
105:           bot.setLayout( new BorderLayout() );
106:           wrapper = new Panel();
107:             wrapper.add( btn );
108:           bot.add( wrapper, "North" );
109:           bot.add( new Label( " " ), "Center" );  // A spacer.
110:           bot.add( footer, "South" );
111:         add( bot, "South" );
112:         errorP.requestFocus();  // Hide the focus.
113: 
114:         // Hook up event listeners:
115:         btn.addActionListener( this );
116:         resetBtn.addActionListener( ( ActionEvent e ) -> { reset(); } );
117:         start();  //  Create and run the two threads
118:     }
119: 
120:     public void start () {
121:         t1 = new Thread( this, "thread1" );
122:         t2 = new Thread( this, "thread2" );
123:         t1.start();
124:         t2.start();
125:     }
126: 
127:     public void stop () {
128:         t1 = t2 = null;  // The threads will check this and die.
129:     }
130: 
131:     private void reset () {
132:         errorP.setVisible( false );
133:         checking.bal = totalBal / 2;
134:         savings.bal = totalBal - checking.bal;
135:         checkingTF.setText( " $" + checking.bal );
136:         savingsTF.setText( " $" + savings.bal );
137:         btn.setLabel( "Start" );
138:         btn.setBackground( Color.green.brighter() );
139:         btn.setEnabled( true );
140:         errorP.requestFocus();  // Hide the focus.
141:         demoRunning = false;
142:         start();
143:     }
144: 
145:     public synchronized void actionPerformed ( ActionEvent e ) {
146:         if ( demoRunning ) {  // then turn it off:
147:             btn.setLabel( "Start" );
148:             btn.setBackground( Color.green.brighter() );
149:         } else {
150:             btn.setLabel( "Stop " );
151:             btn.setBackground( Color.red );
152:         }
153: 
154:         // Threads will check this flag and suspend themselves:
155:         demoRunning = ! demoRunning;
156:         notifyAll();  // Wake up sleeping threads.
157:         errorP.requestFocus();  // Hide the focus.
158:     }
159: 
160:     public void run () {
161:         Thread me = Thread.currentThread();
162:         int amount;
163:         boolean toChecking = true;  // Which way to transfer funds.
164: 
165:         if ( me == t1 )
166:             amount = t1TransferAmount;
167:         else
168:             amount = t2TransferAmount;
169: 
170:         while ( me == t1 || me == t2 ) {  // else time to terminate!
171:             try {  // Pause for 0.1 seconds up to 1.0 seconds:
172:                 Thread.sleep( (int) ( Math.random() * 900 ) + 100 );
173: 
174:                 // Check if demo is suspended (or terminated) yet:
175:                 synchronized( this ) {
176:                     while ( !demoRunning && ( me == t1 || me == t2 ) )
177:                         wait();
178:                 }
179:             }
180:             catch ( InterruptedException ignored ) { }
181: 
182:             if ( me != t1 && me != t2 )  // Check for terminate again.
183:                 break;
184: 
185:             if ( toChecking )
186:                 transfer( checking, savings, amount );
187:             else
188:                 transfer( savings, checking, amount );
189: 
190:             toChecking = ! toChecking;
191: 
192:             synchronized( this ) {
193:                 checkingTF.setText( " $" + checking.bal );
194:                 savingsTF.setText( " $" + savings.bal );
195:                 if ( checking.bal + savings.bal != totalBal ) {
196:                     errorP.setVisible( true );
197:                     stop();
198:                     btn.setEnabled( false ); // Disable Start/Stop btn
199:                     validate();
200:                 }
201:             }
202:         }
203:     }
204: 
205:     // Try the effect of commenting out "synchronized"!
206:     // (The sleep statement just increases the probability of error.)
207: 
208:     private /*synchronized*/ void transfer ( Account toAccount,
209:                                                    Account fromAccount,
210:                                                    int amount )
211:     {
212:         int bal = fromAccount.bal;
213:         pause();
214:         bal -= amount;
215:         fromAccount.bal = bal;
216: 
217:         bal = toAccount.bal;
218:         pause();
219:         bal  += amount;
220:         toAccount.bal = bal;
221:     }
222: 
223:     private void pause () {
224:         if ( Math.random() < 0.35 )
225:             try {  Thread.sleep( 10 ); } catch ( Exception e ) {}
226:     }
227: 
228:     private static class Account {
229:         public int bal;
230: 
231:         Account ( int initialBalance )
232:         {  bal = initialBalance; }
233:     }
234: 
235: }  // End of class Bank